1   /*
2    * Copyright (c) 2005, 2008, Oracle and/or its affiliates. All rights reserved.
3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4    *
5    * This code is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU General Public License version 2 only, as
7    * published by the Free Software Foundation.  Oracle designates this
8    * particular file as subject to the "Classpath" exception as provided
9    * by Oracle in the LICENSE file that accompanied this code.
10   *
11   * This code is distributed in the hope that it will be useful, but WITHOUT
12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14   * version 2 for more details (a copy is included in the LICENSE file that
15   * accompanied this code).
16   *
17   * You should have received a copy of the GNU General Public License version
18   * 2 along with this work; if not, write to the Free Software Foundation,
19   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20   *
21   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22   * or visit www.oracle.com if you need additional information or have any
23   * questions.
24   */
25  
26  package sun.tools.jmap;
27  
28  import java.lang.reflect.Method;
29  import java.io.File;
30  import java.io.IOException;
31  import java.io.InputStream;
32  
33  import com.sun.tools.attach.VirtualMachine;
34  import com.sun.tools.attach.AttachNotSupportedException;
35  import sun.tools.attach.HotSpotVirtualMachine;
36  
37  /*
38   * This class is the main class for the JMap utility. It parses its arguments
39   * and decides if the command should be satisifed using the VM attach mechanism
40   * or an SA tool. At this time the only option that uses the VM attach mechanism
41   * is the -dump option to get a heap dump of a running application. All other
42   * options are mapped to SA tools.
43   */
44  public class JMap {
45  
46      // Options handled by the attach mechanism
47      private static String HISTO_OPTION = "-histo";
48      private static String LIVE_HISTO_OPTION = "-histo:live";
49      private static String DUMP_OPTION_PREFIX = "-dump:";
50  
51      // These options imply the use of a SA tool
52      private static String SA_TOOL_OPTIONS =
53        "-heap|-heap:format=b|-permstat|-finalizerinfo";
54  
55      // The -F (force) option is currently not passed through to SA
56      private static String FORCE_SA_OPTION = "-F";
57  
58      // Default option (if nothing provided)
59      private static String DEFAULT_OPTION = "-pmap";
60  
61      public static void main(String[] args) throws Exception {
62          if (args.length == 0) {
63              usage(); // no arguments
64          }
65  
66          // used to indicate if we should use SA
67          boolean useSA = false;
68  
69          // the chosen option (-heap, -dump:*, ... )
70          String option = null;
71  
72          // First iterate over the options (arguments starting with -).  There should be
73          // one (but maybe two if -F is also used).
74          int optionCount = 0;
75          while (optionCount < args.length) {
76              String arg = args[optionCount];
77              if (!arg.startsWith("-")) {
78                  break;
79              }
80              if (arg.equals(FORCE_SA_OPTION)) {
81                  useSA = true;
82              } else {
83                  if (option != null) {
84                      usage();  // option already specified
85                  }
86                  option = arg;
87              }
88              optionCount++;
89          }
90  
91          // if no option provided then use default.
92          if (option == null) {
93              option = DEFAULT_OPTION;
94          }
95          if (option.matches(SA_TOOL_OPTIONS)) {
96              useSA = true;
97          }
98  
99          // Next we check the parameter count. For the SA tools there are
100         // one or two parameters. For the built-in -dump option there is
101         // only one parameter (the process-id)
102         int paramCount = args.length - optionCount;
103         if (paramCount == 0 || paramCount > 2) {
104             usage();
105         }
106 
107         if (optionCount == 0 || paramCount != 1) {
108             useSA = true;
109         } else {
110             // the parameter for the -dump option is a process-id.
111             // If it doesn't parse to a number then it must be SA
112             // debug server
113             if (!args[optionCount].matches("[0-9]+")) {
114                 useSA = true;
115             }
116         }
117 
118 
119         // at this point we know if we are executing an SA tool or a built-in
120         // option.
121 
122         if (useSA) {
123             // parameters (<pid> or <exe> <core>)
124             String params[] = new String[paramCount];
125             for (int i=optionCount; i<args.length; i++ ){
126                 params[i-optionCount] = args[i];
127             }
128             runTool(option, params);
129 
130         } else {
131             String pid = args[1];
132             // Here we handle the built-in options
133             // As more options are added we should create an abstract tool class and
134             // have a table to map the options
135             if (option.equals(HISTO_OPTION)) {
136                 histo(pid, false);
137             } else if (option.equals(LIVE_HISTO_OPTION)) {
138                 histo(pid, true);
139             } else if (option.startsWith(DUMP_OPTION_PREFIX)) {
140                 dump(pid, option);
141             } else {
142                 usage();
143             }
144         }
145     }
146 
147     // Invoke SA tool  with the given arguments
148     private static void runTool(String option, String args[]) throws Exception {
149         String[][] tools = {
150             { "-pmap",           "sun.jvm.hotspot.tools.PMap"     },
151             { "-heap",           "sun.jvm.hotspot.tools.HeapSummary"     },
152             { "-heap:format=b",  "sun.jvm.hotspot.tools.HeapDumper"      },
153             { "-histo",          "sun.jvm.hotspot.tools.ObjectHistogram" },
154             { "-permstat",       "sun.jvm.hotspot.tools.PermStat"        },
155             { "-finalizerinfo",  "sun.jvm.hotspot.tools.FinalizerInfo"   },
156         };
157 
158         String tool = null;
159 
160         // -dump option needs to be handled in a special way
161         if (option.startsWith(DUMP_OPTION_PREFIX)) {
162             // first check that the option can be parsed
163             String fn = parseDumpOptions(option);
164             if (fn == null) usage();
165 
166             // tool for heap dumping
167             tool = "sun.jvm.hotspot.tools.HeapDumper";
168 
169             // HeapDumper -f <file>
170             args = prepend(fn, args);
171             args = prepend("-f", args);
172         } else {
173             int i=0;
174             while (i < tools.length) {
175                 if (option.equals(tools[i][0])) {
176                     tool = tools[i][1];
177                     break;
178                 }
179                 i++;
180             }
181         }
182         if (tool == null) {
183             usage();   // no mapping to tool
184         }
185 
186         // Tool not available on this  platform.
187         Class<?> c = loadClass(tool);
188         if (c == null) {
189             usage();
190         }
191 
192         // invoke the main method with the arguments
193         Class[] argTypes = { String[].class } ;
194         Method m = c.getDeclaredMethod("main", argTypes);
195 
196         Object[] invokeArgs = { args };
197         m.invoke(null, invokeArgs);
198     }
199 
200     // loads the given class using the system class loader
201     private static Class loadClass(String name) {
202         //
203         // We specify the system clas loader so as to cater for development
204         // environments where this class is on the boot class path but sa-jdi.jar
205         // is on the system class path. Once the JDK is deployed then both
206         // tools.jar and sa-jdi.jar are on the system class path.
207         //
208         try {
209             return Class.forName(name, true,
210                                  ClassLoader.getSystemClassLoader());
211         } catch (Exception x)  { }
212         return null;
213     }
214 
215     private static final String LIVE_OBJECTS_OPTION = "-live";
216     private static final String ALL_OBJECTS_OPTION = "-all";
217     private static void histo(String pid, boolean live) throws IOException {
218         VirtualMachine vm = attach(pid);
219         InputStream in = ((HotSpotVirtualMachine)vm).
220             heapHisto(live ? LIVE_OBJECTS_OPTION : ALL_OBJECTS_OPTION);
221         drain(vm, in);
222     }
223 
224     private static void dump(String pid, String options) throws IOException {
225         // parse the options to get the dump filename
226         String filename = parseDumpOptions(options);
227         if (filename == null) {
228             usage();  // invalid options or no filename
229         }
230 
231         // get the canonical path - important to avoid just passing
232         // a "heap.bin" and having the dump created in the target VM
233         // working directory rather than the directory where jmap
234         // is executed.
235         filename = new File(filename).getCanonicalPath();
236 
237         // dump live objects only or not
238         boolean live = isDumpLiveObjects(options);
239 
240         VirtualMachine vm = attach(pid);
241         System.out.println("Dumping heap to " + filename + " ...");
242         InputStream in = ((HotSpotVirtualMachine)vm).
243             dumpHeap((Object)filename,
244                      (live ? LIVE_OBJECTS_OPTION : ALL_OBJECTS_OPTION));
245         drain(vm, in);
246     }
247 
248     // Parse the options to the -dump option. Valid options are format=b and
249     // file=<file>. Returns <file> if provided. Returns null if <file> not
250     // provided, or invalid option.
251     private static String parseDumpOptions(String arg) {
252         assert arg.startsWith(DUMP_OPTION_PREFIX);
253 
254         String filename = null;
255 
256         // options are separated by comma (,)
257         String options[] = arg.substring(DUMP_OPTION_PREFIX.length()).split(",");
258 
259         for (int i=0; i<options.length; i++) {
260             String option = options[i];
261 
262             if (option.equals("format=b")) {
263                 // ignore format (not needed at this time)
264             } else if (option.equals("live")) {
265                 // a valid suboption
266             } else {
267 
268                 // file=<file> - check that <file> is specified
269                 if (option.startsWith("file=")) {
270                     filename = option.substring(5);
271                     if (filename.length() == 0) {
272                         return null;
273                     }
274                 } else {
275                     return null;  // option not recognized
276                 }
277             }
278         }
279         return filename;
280     }
281 
282     private static boolean isDumpLiveObjects(String arg) {
283         // options are separated by comma (,)
284         String options[] = arg.substring(DUMP_OPTION_PREFIX.length()).split(",");
285         for (String suboption : options) {
286             if (suboption.equals("live")) {
287                 return true;
288             }
289         }
290         return false;
291     }
292 
293     // Attach to <pid>, existing if we fail to attach
294     private static VirtualMachine attach(String pid) {
295         try {
296             return VirtualMachine.attach(pid);
297         } catch (Exception x) {
298             String msg = x.getMessage();
299             if (msg != null) {
300                 System.err.println(pid + ": " + msg);
301             } else {
302                 x.printStackTrace();
303             }
304             if ((x instanceof AttachNotSupportedException) && haveSA()) {
305                 System.err.println("The -F option can be used when the " +
306                   "target process is not responding");
307             }
308             System.exit(1);
309             return null; // keep compiler happy
310         }
311     }
312 
313     // Read the stream from the target VM until EOF, then detach
314     private static void drain(VirtualMachine vm, InputStream in) throws IOException {
315         // read to EOF and just print output
316         byte b[] = new byte[256];
317         int n;
318         do {
319             n = in.read(b);
320             if (n > 0) {
321                 String s = new String(b, 0, n, "UTF-8");
322                 System.out.print(s);
323             }
324         } while (n > 0);
325         in.close();
326         vm.detach();
327     }
328 
329     // return a new string array with arg as the first element
330     private static String[] prepend(String arg, String args[]) {
331         String[] newargs = new String[args.length+1];
332         newargs[0] = arg;
333         System.arraycopy(args, 0, newargs, 1, args.length);
334         return newargs;
335     }
336 
337     // returns true if SA is available
338     private static boolean haveSA() {
339         Class c = loadClass("sun.jvm.hotspot.tools.HeapSummary");
340         return (c != null);
341     }
342 
343     // print usage message
344     private static void usage() {
345         System.out.println("Usage:");
346         if (haveSA()) {
347             System.out.println("    jmap [option] <pid>");
348             System.out.println("        (to connect to running process)");
349             System.out.println("    jmap [option] <executable <core>");
350             System.out.println("        (to connect to a core file)");
351             System.out.println("    jmap [option] [server_id@]<remote server IP or hostname>");
352             System.out.println("        (to connect to remote debug server)");
353             System.out.println("");
354             System.out.println("where <option> is one of:");
355             System.out.println("    <none>               to print same info as Solaris pmap");
356             System.out.println("    -heap                to print java heap summary");
357             System.out.println("    -histo[:live]        to print histogram of java object heap; if the \"live\"");
358             System.out.println("                         suboption is specified, only count live objects");
359             System.out.println("    -permstat            to print permanent generation statistics");
360             System.out.println("    -finalizerinfo       to print information on objects awaiting finalization");
361             System.out.println("    -dump:<dump-options> to dump java heap in hprof binary format");
362             System.out.println("                         dump-options:");
363             System.out.println("                           live         dump only live objects; if not specified,");
364             System.out.println("                                        all objects in the heap are dumped.");
365             System.out.println("                           format=b     binary format");
366             System.out.println("                           file=<file>  dump heap to <file>");
367             System.out.println("                         Example: jmap -dump:live,format=b,file=heap.bin <pid>");
368             System.out.println("    -F                   force. Use with -dump:<dump-options> <pid> or -histo");
369             System.out.println("                         to force a heap dump or histogram when <pid> does not");
370             System.out.println("                         respond. The \"live\" suboption is not supported");
371             System.out.println("                         in this mode.");
372             System.out.println("    -h | -help           to print this help message");
373             System.out.println("    -J<flag>             to pass <flag> directly to the runtime system");
374         } else {
375             System.out.println("    jmap -histo <pid>");
376             System.out.println("      (to connect to running process and print histogram of java object heap");
377             System.out.println("    jmap -dump:<dump-options> <pid>");
378             System.out.println("      (to connect to running process and dump java heap)");
379             System.out.println("");
380             System.out.println("    dump-options:");
381             System.out.println("      format=b     binary default");
382             System.out.println("      file=<file>  dump heap to <file>");
383             System.out.println("");
384             System.out.println("    Example:       jmap -dump:format=b,file=heap.bin <pid>");
385         }
386 
387         System.exit(1);
388     }
389 }